package com.ttx.algorithm.leetcode.thread;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1195. 交替打印字符串
 *
 * 编写一个可以从 1 到 n 输出代表这个数字的字符串的程序，但是：
 *
 *     如果这个数字可以被 3 整除，输出 "fizz"。
 *     如果这个数字可以被 5 整除，输出 "buzz"。
 *     如果这个数字可以同时被 3 和 5 整除，输出 "fizzbuzz"。
 *
 * 例如，当 n = 15，输出： 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。
 *
 * 假设有这么一个类：
 *
 * class FizzBuzz {
 *   public FizzBuzz(int n) { ... }               // constructor
 *   public void fizz(printFizz) { ... }          // only output "fizz"
 *   public void buzz(printBuzz) { ... }          // only output "buzz"
 *   public void fizzbuzz(printFizzBuzz) { ... }  // only output "fizzbuzz"
 *   public void number(printNumber) { ... }      // only output the numbers
 * }
 *
 * 请你实现一个有四个线程的多线程版  FizzBuzz， 同一个 FizzBuzz 实例会被如下四个线程使用：
 *
 *     线程A将调用 fizz() 来判断是否能被 3 整除，如果可以，则输出 fizz。
 *     线程B将调用 buzz() 来判断是否能被 5 整除，如果可以，则输出 buzz。
 *     线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除，如果可以，则输出 fizzbuzz。
 *     线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。
 *
 * 来源：力扣（LeetCode）
 * 链接：https://leetcode-cn.com/problems/fizz-buzz-multithreaded
 * 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
 * @author TimFruit
 * @date 20-4-25 下午7:50
 */
public class P1195FizzBuzzMultithreaded {

    public static void main(String[] args) throws InterruptedException {

        int n=15;
        FizzBuzz fizzBuzz=new FizzBuzz(n);
        PrintContentHolder contentHolder=new PrintContentHolder(n);

        //1
        Thread buzzThread=new Thread(new PrintBuzzRunnable(fizzBuzz,contentHolder));
        buzzThread.start();


        //2
        Thread fizzThread=new Thread(new PrintFizzRunnable(fizzBuzz,contentHolder));
        fizzThread.start();


        //3
        Thread fizzbuzzThread=new Thread(new PrintFizzbuzzRunnable(fizzBuzz,contentHolder));
        fizzbuzzThread.start();

        //4
        Thread numberThread=new Thread(new NumberRunnable(fizzBuzz,contentHolder));
        numberThread.start();


        buzzThread.join();
        fizzThread.join();
        fizzbuzzThread.join();
        numberThread.join();



    }


}

//leetcode目前执行时间最少8ms
//各线程计算预期比较
class FizzBuzz {
    private int n;
    //将要检查判断的数字 不大于n
    private AtomicInteger toCheckNum=new AtomicInteger(1);



    public FizzBuzz(int n) {
        this.n = n;
    }


    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        //x%3==0 && x%5!=0
        int temp;
        for(int x=1;x<=n;x++){
            if(x%3==0 && x%5!=0){
                while((temp=toCheckNum.get())!=x && temp<=n){//自旋等待
                }
                if(temp==x && temp<=n){
                    printFizz.run();
                    toCheckNum.incrementAndGet();
                }

            }
        }
    }

    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        // x%3!=0 && x%5==0
        int temp;
        for(int x=1;x<=n;x++){
            if(x%3!=0 && x%5==0){
                while((temp=toCheckNum.get())!=x && temp<=n){//自旋等待
                }
                if(temp==x && temp<=n){
                    printBuzz.run();
                    toCheckNum.incrementAndGet();
                }
            }
        }
    }

    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        // x%3==0 && x%5==0
        int temp;
        for(int x=1;x<=n;x++){
            if(x%3==0 && x%5==0){
                while((temp=toCheckNum.get())!=x && temp<=n){//自旋等待
                }
                if(temp==x && temp<=n){
                    printFizzBuzz.run();
                    toCheckNum.incrementAndGet();
                }
            }
        }
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        // x%3!=0 && x%5!=0
        int temp;
        for(int x=1;x<=n;x++){
            if(x%3!=0 && x%5!=0){
                while((temp=toCheckNum.get())!=x && temp<=n){//自旋等待
                }
                if(temp==x && temp<=n){
                    printNumber.accept(temp);
                    toCheckNum.incrementAndGet();
                }
            }
        }
    }
}

//
////各线程计算预期比较
//class FizzBuzz {
//    private int n;
//    //将要检查判断的数字 不大于n
//    private AtomicInteger toCheckNum=new AtomicInteger(1);
//
//
//
//    public FizzBuzz(int n) {
//        this.n = n;
//    }
//
//    //1 fizz
//    //2 buzz
//    //3 fizzbuzz
//    //4 x
//    public int check(int x){
//        int threeResult=x%3;
//        int fiveResult=x%5;
//        if(threeResult==0 ){
//            if(fiveResult!=0){
//                //x%3==0 && x%5!=0
//                return 1;
//            }else {
//                //x%3==0 && x%5==0
//                return 3;
//            }
//        }else {
//            if(fiveResult==0){
//                //x%3!=0 && x%5==0
//                return 2;
//            }else {
//                //x%3!=0 && x%5!=0
//                return 4;
//            }
//        }
//    }
//
//    //从startX开始计算符合条件的数字
//    public int calcateN(int startX, int expectResult){
//        for(int i=startX;;i++){
//            if(check(i)==expectResult){
//                return i;
//            }
//        }
//    }
//
//    //符合预期的开始 startExpect 是3的倍数
//    public int calcate1(int startExpect){
//        int temp=startExpect+3;
//        while(check(temp)!=1){
//            temp+=3;
//        }
//        return temp;
//    }
//
//    //符合预期的开始 startExpect 是5的倍数
//    public int calcate2(int startExpect){
//        int temp=startExpect+5;
//        while(check(temp)!=2){
//            temp+=5;
//        }
//        return temp;
//    }
//
//    //符合预期的开始 startExpect 是5的倍数
//    public int calcate3(int startExpect){
//        int temp=startExpect+5;
//        while(check(temp)!=3){
//            temp+=5;
//        }
//        return temp;
//    }
//
//
//    // printFizz.run() outputs "fizz".
//    public void fizz(Runnable printFizz) throws InterruptedException {
//
//        //x%3==0 && x%5!=0
//        final int result=1;
//
//        int tmp=calcateN(toCheckNum.get(),result);
//
//        while(tmp <= n){
//            while (tmp <= n){
//                if(tmp <= n && toCheckNum.get()==tmp){
//                    printFizz.run();
//                    toCheckNum.incrementAndGet();
//                    tmp=calcate1(tmp);
//                    break;
//                }
//            }
//
//        }
//    }
//
//    // printBuzz.run() outputs "buzz".
//    public void buzz(Runnable printBuzz) throws InterruptedException {
//        final int result=2;
//        // x%3!=0 && x%5==0
//        int tmp=calcateN(toCheckNum.get(),result);
//
//        while(tmp <= n){
//            while (tmp <= n){
//                if(tmp <= n && toCheckNum.get()==tmp){
//                    printBuzz.run();
//                    toCheckNum.incrementAndGet();
//                    tmp=calcate2(tmp);
//                    break;
//                }
//            }
//
//        }
//    }
//
//    // printFizzBuzz.run() outputs "fizzbuzz".
//    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
//        final int result=3;
//        // x%3==0 && x%5==0
//        int tmp=calcateN(toCheckNum.get(),result);
//
//        while(tmp <= n){
//            while (tmp <= n){
//                if(tmp <= n && toCheckNum.get()==tmp){
//                    printFizzBuzz.run();
//                    toCheckNum.incrementAndGet();
//                    tmp=calcate3(tmp);
//                    break;
//                }
//            }
//
//        }
//    }
//
//    // printNumber.accept(x) outputs "x", where x is an integer.
//    public void number(IntConsumer printNumber) throws InterruptedException {
//        final int result=4;
//        // x%3!=0 && x%5!=0
//        int tmp=toCheckNum.get();
//
//        while(tmp <= n){
//            tmp=calcateN(tmp, result);
//            while (tmp <= n){
//                if(tmp <= n && toCheckNum.get()==tmp){
//                    printNumber.accept(tmp);
//                    tmp=toCheckNum.incrementAndGet();
//                    break;
//                }
//            }
//        }
//    }
//}

//修改这个类
//解法1
//class FizzBuzz {
//    private int n;
//    //将要检查判断的数字 不大于n
//    private volatile int toCheckNum=1;
//    private Lock lock=new ReentrantLock();
//
//    public FizzBuzz(int n) {
//        this.n = n;
//    }
//
//    //1 fizz
//    //2 buzz
//    //3 fizzbuzz
//    //4 x
//    public int check(int x){
//        if(x%3==0 && x%5!=0){
//            return 1;
//        }
//        if(x%3!=0 && x%5==0){
//            return 2;
//        }
//        if(x%3==0 && x%5==0){
//            return 3;
//        }
//        return 4;
//    }
//
//
//    private boolean isInRange(int x){
//        return x<=n;
//    }
//    // printFizz.run() outputs "fizz".
//    public void fizz(Runnable printFizz) throws InterruptedException {
//        final int result=1;
//        while(isInRange(toCheckNum)){
//            if(check(toCheckNum)==result){
//                lock.lock();
//                if(check(toCheckNum)==result&&isInRange(toCheckNum)){//再次检查
//                    printFizz.run();
//                    toCheckNum++;
//                }
//                lock.unlock();
//            }
//        }
//    }
//
//    // printBuzz.run() outputs "buzz".
//    public void buzz(Runnable printBuzz) throws InterruptedException {
//        final int result=2;
//        while(isInRange(toCheckNum)){
//            if(check(toCheckNum)==result){
//                lock.lock();
//                if(check(toCheckNum)==result && isInRange(toCheckNum)){//再次检查
//                    printBuzz.run();
//                    toCheckNum++;
//                }
//                lock.unlock();
//            }
//        }
//    }
//
//    // printFizzBuzz.run() outputs "fizzbuzz".
//    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
//        final int result=3;
//        while(toCheckNum<=n){
//            if(check(toCheckNum)==result){
//                lock.lock();
//                if(check(toCheckNum)==result && isInRange(toCheckNum) ){//再次检查
//                    printFizzBuzz.run();
//                    toCheckNum++;
//                }
//                lock.unlock();
//            }
//        }
//    }
//
//    // printNumber.accept(x) outputs "x", where x is an integer.
//    public void number(IntConsumer printNumber) throws InterruptedException {
//        final int result=4;
//        while(toCheckNum<=n){
//            if(check(toCheckNum)==result){
//                lock.lock();
//                if(check(toCheckNum)==result && isInRange(toCheckNum)){//再次检查
//                    printNumber.accept(toCheckNum);
//                    toCheckNum++;
//                }
//                lock.unlock();
//            }
//        }
//    }
//}

class PrintContentHolder {


    private ByteArrayOutputStream printContent=new ByteArrayOutputStream();//用于单元测试
    private String enter="\n\r";//换行符

    private int n;//将要判断的个数

    private volatile boolean isEnd;

    private AtomicInteger printCount=new AtomicInteger();

    public PrintContentHolder(int n) {
        this.n = n;
    }

    public ByteArrayOutputStream getPrintContent(){
        return printContent;
    }

    //判断线程输出是否结束
    public boolean isEnd() {
        return isEnd;
    }

    public void println(int x){
        println(String.valueOf(x));
    }

    //统一调用该方法
    public void println(String str){
        try {
            System.out.println(str);
            printContent.write((str+enter).getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        int count=printCount.incrementAndGet();
        if(count>=n){
            isEnd=true;
        }
    }
}


class NumberRunnable implements Runnable{
    private FizzBuzz fizzBuzz;
    private PrintContentHolder printContentHolder;

    public NumberRunnable(FizzBuzz fizzBuzz, PrintContentHolder printContentHolder) {
        this.fizzBuzz = fizzBuzz;
        this.printContentHolder = printContentHolder;
    }

    @Override
    public void run() {
        try {
            while(!printContentHolder.isEnd()){
                fizzBuzz.number((x)->printContentHolder.println(x));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

interface IntConsumer{
    void accept(int x);
}

class PrintFizzRunnable implements Runnable{

    private FizzBuzz fizzBuzz;
    private PrintContentHolder printContentHolder;

    public PrintFizzRunnable(FizzBuzz fizzBuzz, PrintContentHolder printContentHolder) {
        this.fizzBuzz = fizzBuzz;
        this.printContentHolder = printContentHolder;
    }

    public void run() {
        try {
            while(!printContentHolder.isEnd()){
                fizzBuzz.fizz(()->printContentHolder.println("fizz"));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class PrintBuzzRunnable implements Runnable{
    private FizzBuzz fizzBuzz;
    private PrintContentHolder printContentHolder;

    public PrintBuzzRunnable(FizzBuzz fizzBuzz, PrintContentHolder printContentHolder) {
        this.fizzBuzz = fizzBuzz;
        this.printContentHolder = printContentHolder;
    }

    public void run() {
        try {
            while(!printContentHolder.isEnd()){
                fizzBuzz.buzz(()->printContentHolder.println("buzz"));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class PrintFizzbuzzRunnable implements Runnable{
    private FizzBuzz fizzBuzz;
    private PrintContentHolder printContentHolder;

    public PrintFizzbuzzRunnable(FizzBuzz fizzBuzz, PrintContentHolder printContentHolder) {
        this.fizzBuzz = fizzBuzz;
        this.printContentHolder = printContentHolder;
    }

    public void run() {
        try {
            while(!printContentHolder.isEnd()){
                fizzBuzz.fizzbuzz(()->printContentHolder.println("fizzbuzz"));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

